/*
*
* ████████╗░█████╗░███████╗██████╗░░█████╗░
* ╚══██╔══╝██╔══██╗██╔════╝██╔══██╗██╔══██╗
* ░░░██║░░░██║░░██║█████╗░░██████╦╝███████║
* ░░░██║░░░██║░░██║██╔══╝░░██╔══██╗██╔══██║
* ░░░██║░░░╚█████╔╝██║░░░░░██████╦╝██║░░██║
* ░░░╚═╝░░░░╚════╝░╚═╝░░░░░╚═════╝░╚═╝░░╚═╝
* 
*  :: 九方通逊-Henry ::   Copyright©2020 tofba.com  
* 
*/
package com.lmexpress.service.impl;

import java.io.File;
import java.math.BigDecimal;
import java.net.URL;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.jackson.JsonNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.lmexpress.enums.ApiCredential;
import com.lmexpress.enums.ApiOrderError;
import com.lmexpress.enums.LabelFormat;
import com.lmexpress.enums.LabelSuffix;
import com.lmexpress.orm.newgistics.Address;
import com.lmexpress.orm.newgistics.Dimension;
import com.lmexpress.orm.newgistics.Document;
import com.lmexpress.orm.newgistics.NewgisticsOrder;
import com.lmexpress.orm.newgistics.OAuthServiceResponce;
import com.lmexpress.orm.newgistics.Parcel;
import com.lmexpress.orm.newgistics.Rate;
import com.lmexpress.orm.newgistics.Weight;
import com.lmexpress.orm.openapi.ExpressOrderApi;
import com.lmexpress.orm.openapi.OrderInvoice;
import com.lmexpress.orm.openapi.OrderVolume;
import com.lmexpress.orm.rest.ApiLabelResponse;
import com.lmexpress.orm.rest.ApiOrderResponse;
import com.lmexpress.service.ApiService;
import com.lmexpress.util.ApiUtil;
import com.lmexpress.util.HttpClientPoolUtil;
import com.lmexpress.util.HttpClientResult;
import com.lmexpress.util.JsonConverter;
import com.lmexpress.util.PropertiesUtil;

/**
 * 
 * PitneyBowes对接 
 * <a href="https://shipping.pitneybowes.com/shipments.html">pitneybowes文档</a>
 * 
 * @author Henry(fba02)
 * @version [版本号, 2020-12-09]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
public class NewgisticsClient implements ApiService {
    private Logger LOGGER = LoggerFactory.getLogger(NewgisticsClient.class);
    private final static Double CHANGE_SERVICE_REQUESTED_CRITICAL_VALUE = 0.4536D;    
    private final static String CHANGE_SERVICE_REQUESTED = "ChangeServiceRequested";
    /**
     * <按美东,美西两个分区区分,ONT:安大略,EWR：纽瓦克(新泽西)>
     */
    private final static String ZONE_E_W = "{\"EWR\":[\"005\",\"006\",\"007\",\"008\",\"009\",\"010\",\"011\",\"012\",\"013\",\"014\",\"015\",\"016\",\"017\",\"018\",\"019\",\"020\",\"021\",\"022\",\"023\",\"024\",\"025\",\"026\",\"027\",\"028\",\"029\",\"030\",\"031\",\"032\",\"033\",\"034\",\"035\",\"036\",\"037\",\"038\",\"039\",\"040\",\"041\",\"042\",\"043\",\"044\",\"045\",\"046\",\"047\",\"048\",\"049\",\"050\",\"051\",\"052\",\"053\",\"054\",\"055\",\"056\",\"057\",\"058\",\"059\",\"060\",\"061\",\"062\",\"063\",\"064\",\"065\",\"066\",\"067\",\"068\",\"069\",\"070\",\"071\",\"072\",\"073\",\"074\",\"075\",\"076\",\"077\",\"078\",\"079\",\"080\",\"081\",\"082\",\"083\",\"084\",\"085\",\"086\",\"087\",\"088\",\"089\",\"090\",\"091\",\"092\",\"093\",\"094\",\"095\",\"096\",\"097\",\"098\",\"099\",\"100\",\"101\",\"102\",\"103\",\"104\",\"105\",\"106\",\"107\",\"108\",\"109\",\"110\",\"111\",\"112\",\"113\",\"114\",\"115\",\"116\",\"117\",\"118\",\"119\",\"120\",\"121\",\"122\",\"123\",\"124\",\"125\",\"126\",\"127\",\"128\",\"129\",\"130\",\"131\",\"132\",\"133\",\"134\",\"135\",\"136\",\"137\",\"138\",\"139\",\"140\",\"141\",\"142\",\"143\",\"144\",\"145\",\"146\",\"147\",\"148\",\"149\",\"150\",\"151\",\"152\",\"153\",\"154\",\"155\",\"156\",\"157\",\"158\",\"159\",\"160\",\"161\",\"162\",\"163\",\"164\",\"165\",\"166\",\"167\",\"168\",\"169\",\"170\",\"171\",\"172\",\"173\",\"174\",\"175\",\"176\",\"177\",\"178\",\"179\",\"180\",\"181\",\"182\",\"183\",\"184\",\"185\",\"186\",\"187\",\"188\",\"189\",\"190\",\"191\",\"192\",\"193\",\"194\",\"195\",\"196\",\"197\",\"198\",\"199\",\"200\",\"201\",\"202\",\"203\",\"204\",\"205\",\"206\",\"207\",\"208\",\"209\",\"210\",\"211\",\"212\",\"213\",\"214\",\"215\",\"216\",\"217\",\"218\",\"219\",\"220\",\"221\",\"222\",\"223\",\"224\",\"225\",\"226\",\"227\",\"228\",\"229\",\"230\",\"231\",\"232\",\"233\",\"234\",\"235\",\"236\",\"237\",\"238\",\"239\",\"240\",\"241\",\"242\",\"243\",\"244\",\"245\",\"246\",\"247\",\"248\",\"249\",\"250\",\"251\",\"252\",\"253\",\"254\",\"255\",\"256\",\"257\",\"258\",\"259\",\"260\",\"261\",\"262\",\"263\",\"264\",\"265\",\"266\",\"267\",\"268\",\"269\",\"270\",\"271\",\"272\",\"273\",\"274\",\"275\",\"276\",\"277\",\"278\",\"279\",\"280\",\"281\",\"282\",\"283\",\"284\",\"285\",\"286\",\"287\",\"288\",\"289\",\"290\",\"291\",\"292\",\"293\",\"294\",\"295\",\"296\",\"297\",\"298\",\"299\",\"300\",\"301\",\"302\",\"303\",\"304\",\"305\",\"306\",\"307\",\"308\",\"309\",\"310\",\"311\",\"312\",\"313\",\"314\",\"315\",\"316\",\"317\",\"318\",\"319\",\"320\",\"321\",\"322\",\"323\",\"324\",\"325\",\"326\",\"327\",\"328\",\"329\",\"330\",\"331\",\"332\",\"333\",\"334\",\"335\",\"336\",\"337\",\"338\",\"339\",\"340\",\"341\",\"342\",\"343\",\"344\",\"345\",\"346\",\"347\",\"348\",\"349\",\"350\",\"351\",\"352\",\"353\",\"354\",\"355\",\"356\",\"357\",\"358\",\"359\",\"360\",\"361\",\"362\",\"363\",\"364\",\"365\",\"366\",\"367\",\"368\",\"369\",\"370\",\"371\",\"372\",\"373\",\"374\",\"375\",\"376\",\"377\",\"378\",\"379\",\"380\",\"381\",\"382\",\"383\",\"384\",\"385\",\"386\",\"387\",\"388\",\"389\",\"390\",\"391\",\"392\",\"393\",\"394\",\"395\",\"396\",\"397\",\"398\",\"399\",\"400\",\"401\",\"402\",\"403\",\"404\",\"405\",\"406\",\"407\",\"408\",\"409\",\"410\",\"411\",\"412\",\"413\",\"414\",\"415\",\"416\",\"417\",\"418\",\"419\",\"420\",\"421\",\"422\",\"423\",\"424\",\"425\",\"426\",\"427\",\"428\",\"429\",\"430\",\"431\",\"432\",\"433\",\"434\",\"435\",\"436\",\"437\",\"438\",\"439\",\"440\",\"441\",\"442\",\"443\",\"444\",\"445\",\"446\",\"447\",\"448\",\"449\",\"450\",\"451\",\"452\",\"453\",\"454\",\"455\",\"456\",\"457\",\"458\",\"459\",\"460\",\"461\",\"462\",\"463\",\"464\",\"465\",\"466\",\"467\",\"468\",\"469\",\"470\",\"471\",\"472\",\"473\",\"474\",\"475\",\"476\",\"477\",\"478\",\"479\",\"480\",\"481\",\"482\",\"483\",\"484\",\"485\",\"486\",\"487\",\"488\",\"489\",\"490\",\"491\",\"492\",\"493\",\"494\",\"495\",\"496\",\"497\",\"498\",\"499\",\"500\",\"501\",\"502\",\"503\",\"504\",\"505\",\"506\",\"507\",\"508\",\"509\",\"510\",\"511\",\"512\",\"513\",\"514\",\"515\",\"516\",\"517\",\"518\",\"519\",\"520\",\"521\",\"522\",\"523\",\"524\",\"525\",\"526\",\"527\",\"528\",\"529\",\"530\",\"531\",\"532\",\"533\",\"534\",\"535\",\"536\",\"537\",\"538\",\"539\",\"540\",\"541\",\"542\",\"543\",\"544\",\"545\",\"546\",\"547\",\"548\",\"549\",\"550\",\"551\",\"552\",\"553\",\"554\",\"555\",\"556\",\"557\",\"558\",\"559\",\"560\",\"561\",\"562\",\"563\",\"564\",\"565\",\"566\",\"567\",\"568\",\"569\",\"570\",\"571\",\"572\",\"573\",\"574\",\"575\",\"576\",\"577\",\"578\",\"579\",\"580\",\"581\",\"582\",\"583\",\"584\",\"585\",\"586\",\"587\",\"588\",\"589\",\"600\",\"601\",\"602\",\"603\",\"604\",\"605\",\"606\",\"607\",\"608\",\"609\",\"610\",\"611\",\"612\",\"613\",\"614\",\"615\",\"616\",\"617\",\"618\",\"619\",\"620\",\"621\",\"622\",\"623\",\"624\",\"625\",\"626\",\"627\",\"628\",\"629\",\"630\",\"631\",\"632\",\"633\",\"634\",\"635\",\"636\",\"637\",\"638\",\"639\",\"640\",\"641\",\"642\",\"643\",\"644\",\"645\",\"646\",\"647\",\"648\",\"649\",\"650\",\"651\",\"652\",\"653\",\"654\",\"655\",\"656\",\"657\",\"658\",\"659\",\"660\",\"661\",\"662\",\"663\",\"664\",\"665\",\"666\",\"667\",\"668\",\"669\",\"670\",\"671\",\"672\",\"673\",\"674\",\"675\",\"676\",\"677\",\"678\",\"679\",\"680\",\"681\",\"682\",\"683\",\"684\",\"685\",\"686\",\"687\",\"688\",\"689\",\"690\",\"691\",\"692\",\"693\",\"694\",\"695\",\"696\",\"697\",\"698\",\"699\"],\"ONT\":[\"590\",\"591\",\"592\",\"593\",\"594\",\"595\",\"596\",\"597\",\"598\",\"599\",\"700\",\"701\",\"702\",\"703\",\"704\",\"705\",\"706\",\"707\",\"708\",\"709\",\"710\",\"711\",\"712\",\"713\",\"714\",\"715\",\"716\",\"717\",\"718\",\"719\",\"720\",\"721\",\"722\",\"723\",\"724\",\"725\",\"726\",\"727\",\"728\",\"729\",\"730\",\"731\",\"732\",\"733\",\"734\",\"735\",\"736\",\"737\",\"738\",\"739\",\"740\",\"741\",\"742\",\"743\",\"744\",\"745\",\"746\",\"747\",\"748\",\"749\",\"750\",\"751\",\"752\",\"753\",\"754\",\"755\",\"756\",\"757\",\"758\",\"759\",\"760\",\"761\",\"762\",\"763\",\"764\",\"765\",\"766\",\"767\",\"768\",\"769\",\"770\",\"771\",\"772\",\"773\",\"774\",\"775\",\"776\",\"777\",\"778\",\"779\",\"780\",\"781\",\"782\",\"783\",\"784\",\"785\",\"786\",\"787\",\"788\",\"789\",\"790\",\"791\",\"792\",\"793\",\"794\",\"795\",\"796\",\"797\",\"798\",\"799\",\"800\",\"801\",\"802\",\"803\",\"804\",\"805\",\"806\",\"807\",\"808\",\"809\",\"810\",\"811\",\"812\",\"813\",\"814\",\"815\",\"816\",\"817\",\"818\",\"819\",\"820\",\"821\",\"822\",\"823\",\"824\",\"825\",\"826\",\"827\",\"828\",\"829\",\"830\",\"831\",\"832\",\"833\",\"834\",\"835\",\"836\",\"837\",\"838\",\"839\",\"840\",\"841\",\"842\",\"843\",\"844\",\"845\",\"846\",\"847\",\"848\",\"849\",\"850\",\"851\",\"852\",\"853\",\"854\",\"855\",\"856\",\"857\",\"858\",\"859\",\"860\",\"861\",\"862\",\"863\",\"864\",\"865\",\"866\",\"867\",\"868\",\"869\",\"870\",\"871\",\"872\",\"873\",\"874\",\"875\",\"876\",\"877\",\"878\",\"879\",\"880\",\"881\",\"882\",\"883\",\"884\",\"885\",\"886\",\"887\",\"888\",\"889\",\"890\",\"891\",\"892\",\"893\",\"894\",\"895\",\"896\",\"897\",\"898\",\"899\",\"900\",\"901\",\"902\",\"903\",\"904\",\"905\",\"906\",\"907\",\"908\",\"909\",\"910\",\"911\",\"912\",\"913\",\"914\",\"915\",\"916\",\"917\",\"918\",\"919\",\"920\",\"921\",\"922\",\"923\",\"924\",\"925\",\"926\",\"927\",\"928\",\"929\",\"930\",\"931\",\"932\",\"933\",\"934\",\"935\",\"936\",\"937\",\"938\",\"939\",\"940\",\"941\",\"942\",\"943\",\"944\",\"945\",\"946\",\"947\",\"948\",\"949\",\"950\",\"951\",\"952\",\"953\",\"954\",\"955\",\"956\",\"957\",\"958\",\"959\",\"960\",\"961\",\"962\",\"963\",\"964\",\"965\",\"966\",\"967\",\"968\",\"969\",\"970\",\"971\",\"972\",\"973\",\"974\",\"975\",\"976\",\"977\",\"978\",\"979\",\"980\",\"981\",\"982\",\"983\",\"984\",\"985\",\"986\",\"987\",\"988\",\"989\",\"990\",\"991\",\"992\",\"993\",\"994\",\"995\",\"996\",\"997\",\"998\",\"999\"]}";
    private static Map<String, String> FACILITY_CODE = new HashMap<String, String>();
    private static Map<String, List<String>> ZONE_MAP = new HashMap<String, List<String>>();
    
    static {
        FACILITY_CODE.put("LAX", "1304");
        FACILITY_CODE.put("ONT", "1787");
        FACILITY_CODE.put("ATL", "0224");
        FACILITY_CODE.put("ORD", "1094");
        FACILITY_CODE.put("ELZ", "2601");
        FACILITY_CODE.put("EWR", "1779");
        FACILITY_CODE.put("DFW", "1361");
        FACILITY_CODE.put("SFO", "1548");
        FACILITY_CODE.put("IND", "1585");
        FACILITY_CODE.put("CVG", "0921");
        FACILITY_CODE.put("HVN", "1572");
        FACILITY_CODE.put("RNO", "1328");
        FACILITY_CODE.put("PHL", "1532");
        ZONE_MAP = new Gson().fromJson(ZONE_E_W, new TypeToken<Map<String, List<String>>>() {
        }.getType());
    }    
    
    @Override
    public ApiOrderResponse preProcessCreateOrder(ExpressOrderApi expressOrderApi) {
        String postCode = expressOrderApi.getConsigneePostCode();
        if (StringUtils.isBlank(postCode)) {
            return new ApiOrderResponse(false, null, PropertiesUtil.getProperty("newgistics.postcode.required"), false, null);
        }
        if (postCode.length() < 5) {
            return new ApiOrderResponse(false, null, PropertiesUtil.getProperty("newgistics.postcode.less.than.three.char"), false, null);
        }
        String token = expressOrderApi.getApiCredentials().get(ApiCredential.NEWGISTICS_ACCESS_TOKEN.getKey());
        if (StringUtils.isBlank(token)) {
            return new ApiOrderResponse(false, null, PropertiesUtil.getProperty("newgistics.access.token.required"), false, null);
        }
        return null;
    }

    @Override
    public String mapping(ExpressOrderApi expressOrderApi) {
        Map<String, String> apiCredentials = expressOrderApi.getApiCredentials();
        NewgisticsOrder newgisticsOrder = new NewgisticsOrder();
        // 发件人->退件地址
        Address fromAddress = new Address();
        List<String> fromAddressLines = new ArrayList<String>();
        fromAddress.setName(apiCredentials.get(ApiCredential.NEWGISTICS_FORM_ADDRESS_NAME.getKey()));
        fromAddress.setCompany(apiCredentials.get(ApiCredential.NEWGISTICS_FORM_ADDRESS_COMPANY.getKey()));
        fromAddress.setCityTown(apiCredentials.get(ApiCredential.NEWGISTICS_FORM_ADDRESS_CITYTOWN.getKey()));
        fromAddress.setStateProvince(apiCredentials.get(ApiCredential.NEWGISTICS_FORM_ADDRESS_STATEPROVINCE.getKey()));
        fromAddress.setPostalCode(apiCredentials.get(ApiCredential.NEWGISTICS_FORM_ADDRESS_POSTALCODE.getKey()));
        fromAddress.setCountryCode(apiCredentials.get(ApiCredential.NEWGISTICS_FORM_ADDRESS_COUNTRYCODE.getKey()));
        fromAddressLines.add(apiCredentials.get(ApiCredential.NEWGISTICS_FORM_ADDRESS_LINE.getKey()));
        fromAddress.setAddressLines(fromAddressLines);
        // 收件人
        Address toAddress = new Address();
        toAddress.setName(expressOrderApi.getConsigneeName());
        toAddress.setCityTown(expressOrderApi.getConsigneeCity());
        toAddress.setStateProvince(expressOrderApi.getConsigneeState());
        String phone = expressOrderApi.getConsigneeTelephone();
        if (StringUtils.isBlank(phone)) {
            phone = expressOrderApi.getConsigneeMobile();
        }
        toAddress.setPhone(phone);
        toAddress.setPostalCode(expressOrderApi.getConsigneePostCode());
        toAddress.setCompany(expressOrderApi.getConsigneeCompanyName());
        toAddress.setEmail(expressOrderApi.getConsigneeEmail());
        toAddress.setCountryCode(expressOrderApi.getCountryCode());
        List<String> toAddressLines = new ArrayList<String>();
        String consigneeAddress = expressOrderApi.getConsigneeAddress();
        if (StringUtils.isNotBlank(consigneeAddress)) {
            toAddressLines.add(consigneeAddress);
        }
        if (CollectionUtils.isNotEmpty(toAddressLines)) {
            toAddress.setAddressLines(toAddressLines);
        }
        Parcel parcel = new Parcel();
        Weight weight = new Weight();
        if (null != expressOrderApi.getWeight()) {
            weight.setWeight(BigDecimal.valueOf(ApiUtil.round(expressOrderApi.getWeight() * 1000, 3)));
        }
        parcel.setWeight(weight);
        parcel.setCurrencyCode(expressOrderApi.getCurrencyCode());
        Double cargoAmount = expressOrderApi.getCargoAmount();
        if (null != cargoAmount) {
            parcel.setValueOfGoods(BigDecimal.valueOf(cargoAmount));
        }
        Double invoiceTotalWeight = 0D;
        StringBuffer deliveryInformationBuffer = new StringBuffer();
        List<OrderInvoice> orderInvoices = expressOrderApi.getOrderInvoices();
        if (CollectionUtils.isNotEmpty(orderInvoices)) {
            for (OrderInvoice orderInvoiceParam : orderInvoices) {
                Double invoiceWeight = orderInvoiceParam.getInvoiceWeight();
                Integer pcs = orderInvoiceParam.getInvoicePcs() == null ? 1 : orderInvoiceParam.getInvoicePcs();
                if (null != invoiceWeight) {
                    invoiceTotalWeight += invoiceWeight * pcs;
                }
                deliveryInformationBuffer.append(orderInvoiceParam.getSkuCode()).append("*").append(pcs).append(";");
            }
        }
        if (null == weight.getWeight()) {
            if (invoiceTotalWeight > 0D) {
                weight.setWeight(BigDecimal.valueOf(invoiceTotalWeight * 1000));
            }
        }
        List<OrderVolume> orderVolumeParams = expressOrderApi.getOrderVolumes();
        Dimension dimension = new Dimension();
        dimension.setUnitOfMeasurement("CM");
        dimension.setIrregularParcelGirth("20");
        if (CollectionUtils.isEmpty(orderVolumeParams)) {
            dimension.setHeight(new BigDecimal(10));
            dimension.setLength(new BigDecimal(10));
            dimension.setWidth(new BigDecimal(10));
        } else {
            for (OrderVolume orderVolumeParam : orderVolumeParams) {
                Double height = orderVolumeParam.getHeight();
                Double length = orderVolumeParam.getLength();
                Double width = orderVolumeParam.getWidth();
                if (null != height) {
                    dimension.setHeight(BigDecimal.valueOf(height));
                }
                if (null != length) {
                    dimension.setLength(BigDecimal.valueOf(length));
                }
                if (null != width) {
                    dimension.setWidth(BigDecimal.valueOf(width));
                }
                break;
            }
            if (null == weight.getWeight()) {
                Double totalWeight = 0D;
                for (OrderVolume orderVolumeParam : orderVolumeParams) {
                    Double orderWeight = orderVolumeParam.getWeight();
                    if (null != orderWeight) {
                        totalWeight += orderWeight;
                    }
                }
                weight.setWeight(BigDecimal.valueOf(totalWeight * 1000));
            }
        }
        if (null == weight.getWeight()) {
            weight.setWeight(BigDecimal.valueOf(100));
        }
        parcel.setDimension(dimension);
        List<Rate> rates = new ArrayList<Rate>();
        Rate rate = new Rate();
        rates.add(rate);
        List<Document> documents = new ArrayList<Document>();
        Document document = new Document();
        documents.add(document);
        newgisticsOrder.setDocuments(documents);
        newgisticsOrder.setRates(rates);
        newgisticsOrder.setParcel(parcel);
        newgisticsOrder.setFromAddress(fromAddress);
        newgisticsOrder.setToAddress(toAddress);
        // final String shipperId = "3000079581";
        // final Integer clientFacilityId = 1777;
        String facilityCode = "";
        String carrierFacilityId = null;
        String postCode = expressOrderApi.getConsigneePostCode();
        if (StringUtils.isNotBlank(postCode) && postCode.length() > 3) {
            String postCodePrefix = postCode.substring(0, 3);
            for (Entry<String, List<String>> entry : ZONE_MAP.entrySet()) {
                String key = entry.getKey();
                List<String> value = entry.getValue();
                if (value.contains(postCodePrefix)) {
                    facilityCode = key;
                    carrierFacilityId = FACILITY_CODE.get(key);
                    break;
                }
            }
        }
        if (StringUtils.isBlank(facilityCode)) {
            apiCredentials.get(ApiCredential.NEWGISTICS_FACILITY_CODE.getKey());
            if (StringUtils.isBlank(facilityCode)) {
                facilityCode = ApiCredential.NEWGISTICS_FACILITY_CODE.getValue();
            }
        }
        if (StringUtils.isBlank(carrierFacilityId)) {
            carrierFacilityId = FACILITY_CODE.get(facilityCode);
        }
        // --------------------------------------------------------------------------- ShipmentOptions
        List<Map<String, Object>> shipmentOptions = new ArrayList<Map<String, Object>>();
        Map<String, Object> shipperIdMap = new HashMap<String, Object>();
        shipperIdMap.put("name", "SHIPPER_ID");
        shipperIdMap.put("value", apiCredentials.get(ApiCredential.NEWGISTICS_SHIPPER_ID.getKey()));
        shipmentOptions.add(shipperIdMap);
        
        Map<String, Object> clientFacilityIdMap = new HashMap<String, Object>();
        clientFacilityIdMap.put("name", "CLIENT_FACILITY_ID");
        clientFacilityIdMap.put("value", apiCredentials.get(ApiCredential.NEWGISTICS_SHIPMENTS_CLIENT_FACILITY_ID.getKey()));// ssigned by Newgistics to the merchant. If testing in sandbox, set this to 0093.
        shipmentOptions.add(clientFacilityIdMap);
        
        Map<String, Object> carrierFacilityIdMap = new HashMap<String, Object>();
        carrierFacilityIdMap.put("name", "CARRIER_FACILITY_ID");
        carrierFacilityIdMap.put("value", carrierFacilityId);// assigned by Newgistics to the merchant. If testing in sandbox, set this to 1585.
        shipmentOptions.add(carrierFacilityIdMap);
        // 可选
        Map<String, Object> isRectangularMap = new HashMap<String, Object>();
        isRectangularMap.put("name", "IS_RECTANGULAR");
        isRectangularMap.put("value", "false");
        shipmentOptions.add(isRectangularMap);
        // 面单退回操作
        String newgisticsNonDeliveryOption = apiCredentials.get(ApiCredential.NEWGISTICS_NON_DELIVERY_OPTION.getKey());
        if (StringUtils.isNotBlank(newgisticsNonDeliveryOption)) {
            Map<String, Object> nonDeliveryOptionMap = new HashMap<String, Object>();
            nonDeliveryOptionMap.put("name", "NON_DELIVERY_OPTION");
            nonDeliveryOptionMap.put("value", newgisticsNonDeliveryOption);
            shipmentOptions.add(nonDeliveryOptionMap);
        } else {
            // 按照重量来计算是否退回0.4536kg
            // 一磅以下的不显示change service request， 一磅以上的显示change service request
            Double weightCount = expressOrderApi.getWeight();
            if (weightCount != null && weightCount > CHANGE_SERVICE_REQUESTED_CRITICAL_VALUE) {
                Map<String, Object> nonDeliveryOptionMap = new HashMap<String, Object>();
                nonDeliveryOptionMap.put("name", "NON_DELIVERY_OPTION");
                nonDeliveryOptionMap.put("value", CHANGE_SERVICE_REQUESTED);
                shipmentOptions.add(nonDeliveryOptionMap);
            }
        }
        // 配货信息添加到面单
        String deliveryInformation = expressOrderApi.getCustomerInvoiceCode() + " " + deliveryInformationBuffer.toString();
        if (StringUtils.isNotBlank(deliveryInformation) && deliveryInformation.endsWith(";")) {
            deliveryInformation = deliveryInformation.substring(0, deliveryInformation.length() - 1);
        }
        Map<String, Object> printCustomMessageMap = new HashMap<String, Object>();
        printCustomMessageMap.put("name", "PRINT_CUSTOM_MESSAGE_2");
        String productCode = expressOrderApi.getProductCode();
        String printCustomMessage2 = productCode + " " + expressOrderApi.getCustomerCode() + " " + facilityCode + " " + expressOrderApi.getCustomerInvoiceCode() + " " + deliveryInformation;
        // 截取最后一个SKU,最多截取
        if (printCustomMessage2.length() > 65) {
            printCustomMessage2 = productCode + " " + expressOrderApi.getCustomerCode() + " " + facilityCode + " " + expressOrderApi.getCustomerInvoiceCode() + " " + deliveryInformation.substring(0, deliveryInformation.lastIndexOf(";"));
        }
        printCustomMessageMap.put("value", printCustomMessage2);
        shipmentOptions.add(printCustomMessageMap);
        newgisticsOrder.setShipmentOptions(shipmentOptions);
        // --------------------------------------------------------------------------- References
        String referenceNumber = expressOrderApi.getCustomerInvoiceCode();
        String addlRef1 = expressOrderApi.getProductId() == null ? expressOrderApi.getProductCode() : expressOrderApi.getProductId().toString();
        Object addlRef2 = expressOrderApi.getCustomerCode();
        List<Map<String, Object>> references = new ArrayList<Map<String, Object>>();
        if (StringUtils.isNotBlank(referenceNumber)) {
            Map<String, Object> referenceNumberMap = new HashMap<String, Object>();
            referenceNumberMap.put("name", "ReferenceNumber");
            referenceNumberMap.put("value", referenceNumber);
            references.add(referenceNumberMap);
        }
        if (null != addlRef1) {
            Map<String, Object> addlRef1Map = new HashMap<String, Object>();
            addlRef1Map.put("name", "AddlRef1");
            addlRef1Map.put("value", addlRef1);
            references.add(addlRef1Map);
        }
        if (null != addlRef2) {
            Map<String, Object> addlRef2Map = new HashMap<String, Object>();
            addlRef2Map.put("name", "AddlRef2");
            addlRef2Map.put("value", addlRef2);
            references.add(addlRef2Map);
        }
        newgisticsOrder.setReferences(references);
        return new Gson().toJson(newgisticsOrder);
    }

    @Override
    public ApiOrderResponse createOrder(ExpressOrderApi expressOrderApi) {
        try {
            String orderJson = mapping(expressOrderApi);
            LOGGER.info("newgistics.shipment.request{}", orderJson);
            Map<String, String> headerMap = new HashMap<String, String>();
            headerMap.put("Content-type", "application/json");
            String token = expressOrderApi.getApiCredentials().get(ApiCredential.NEWGISTICS_ACCESS_TOKEN.getKey());
            headerMap.put("Authorization", "Bearer " + token);
            headerMap.put("X-PB-TransactionId", expressOrderApi.getCustomerInvoiceCode());
            String responseJson = null;
            HttpClientResult httpClientResult = HttpClientPoolUtil.doPost(ApiCredential.NEWGISTICS_SHIPMENTS.getValue(), headerMap, orderJson);
            if (null == httpClientResult) {
                return new ApiOrderResponse(false, null, ApiOrderError.SERVICE_NO_DATA_RETURN.getMessage(), true, null);
            }
            responseJson = httpClientResult.getContent();
            LOGGER.info("newgistics.shipment.rest.response:{}", responseJson);
            if (StringUtils.isBlank(responseJson)) {
                return new ApiOrderResponse(false, null,  ApiOrderError.SERVICE_NO_DATA_RETURN.getMessage(), true, responseJson);
            }
            boolean repost = false;
            JsonNode jsonNode = JsonConverter.tree(responseJson);
            // 错误消息返回数组,对接成功返回对象
            if (jsonNode.isArray()) {
                List<JsonNode> messageNodes = jsonNode.findValues("message");
                List<JsonNode> errorCodeNodes = jsonNode.findValues("errorCode");
                List<JsonNode> additionalInfoNodes = jsonNode.findValues("additionalInfo");
                if (CollectionUtils.isNotEmpty(errorCodeNodes)) {
                    for (JsonNode errorCodeNode : errorCodeNodes) {
                        String errorCode = errorCodeNode.findPath("errorCode").asText();
                        // 令牌过期
                        if (Objects.equals(errorCode, "40100004") || Objects.equals(errorCode, "40100005")) {
                            repost = true;
                            break;
                        }
                    }
                }
                StringBuilder errorMsgBuilder = new StringBuilder();
                if (CollectionUtils.isNotEmpty(messageNodes)) {
                    for (JsonNode messageNode : messageNodes) {
                        errorMsgBuilder.append(messageNode.getTextValue()).append(",");
                    }
                }
                if (CollectionUtils.isNotEmpty(additionalInfoNodes)) {
                    for (JsonNode additionalInfoNode : additionalInfoNodes) {
                        errorMsgBuilder.append(additionalInfoNode.getTextValue());
                    }
                }
                if (repost) {
                    return new ApiOrderResponse(false, null, errorMsgBuilder.toString(), true, responseJson);
                } else {
                    return new ApiOrderResponse(false, null, errorMsgBuilder.toString(), false, responseJson);
                }
            } 
            if (jsonNode.has("errors")) {
                JsonNode errorsNode = jsonNode.findValue("errors");
                JsonNode errorCodeNode = errorsNode.findValue("errorCode");
                JsonNode errorDescriptionNode = errorsNode.findValue("errorDescription");
                String errorCode = errorCodeNode.getTextValue();
                String errorDescription = "";
                if (errorDescriptionNode != null) {
                    errorDescription = errorDescriptionNode.getTextValue();
                }
                if (Objects.equals(errorCode, "PB-APIM-ERR-1003") || Objects.equals(errorCode, "PB-APIM-ERR-1002")) {
                    repost = true;
                }
                if (repost) {
                    return new ApiOrderResponse(false, null, errorDescription, true, responseJson);
                } else {
                    return new ApiOrderResponse(false, null, errorDescription, false, responseJson);
                }
            }
            // 追踪号节点
            JsonNode parcelTrackingNumberNode = jsonNode.findValue("parcelTrackingNumber");
            String parcelTrackingNumber = parcelTrackingNumberNode.asText();
            Integer length = parcelTrackingNumber.length();
            if (length > 26) {
                parcelTrackingNumber = parcelTrackingNumber.substring(length - 26, length);
            }
            ApiOrderResponse apiOrderResponse = new ApiOrderResponse(true, parcelTrackingNumber, responseJson);
            // 面单路径节点
            JsonNode documentsNode = jsonNode.findValue("documents");
            if (null == documentsNode) {
                return apiOrderResponse;
            }
            JsonNode contentsNode = documentsNode.findValue("contents");
            if (null == contentsNode) {
                return apiOrderResponse;
            }
            String contents = contentsNode.asText();// PDF
            if (StringUtils.isBlank(contents)) {
                return apiOrderResponse;
            }
            String pdfName = parcelTrackingNumber + LabelSuffix.PDF.getSuffix();
            String centrePath = ApiUtil.getCurrentDay() + File.separator;
            String relativePath = DEFAULT_LABEL_FOLDER + File.separator + centrePath;
            String folderPath = DEFAULT_LABEL_ROOT + relativePath;
            if (!new File(folderPath).exists()) {
                new File(folderPath).mkdirs();
            }
            String fileUrl = folderPath + pdfName;
            try {
                FileUtils.copyURLToFile(new URL(contents), new File(fileUrl));// 下载面单到本地
                apiOrderResponse.setLmLabelUrl(relativePath + pdfName);
            } catch (Exception e) {
                LOGGER.error("newgistics.copy.url.to.file.exception", e);
            }
            apiOrderResponse.setLabelUrl(contents);
            JsonNode shipmentIdNode = jsonNode.findValue("shipmentId");
            if (null != shipmentIdNode) {
                String shipmentId = shipmentIdNode.asText();
                apiOrderResponse.setThirdPartyorderNo(shipmentId);
            }
            return apiOrderResponse;        
        } catch (Exception e) {
            LOGGER.error("newgistics.shipment.exception", e);
            return new ApiOrderResponse(false, null, ApiOrderError.CREATE_ORDER_EXCEPTION.getMessage(), false, e.getMessage());
        }
    }

    @Override
    public ApiLabelResponse label(String trackingNumber, Map<String, String> configure) {
        try {
            if (StringUtils.isBlank(trackingNumber)) {
                return new ApiLabelResponse(trackingNumber, null);
            }
            String url = ApiCredential.NEWGISTICS_LABEL.getValue();
            url = url.replace("{trackingNumber}", trackingNumber);
            String token = configure.get(ApiCredential.NEWGISTICS_ACCESS_TOKEN.getKey());
            Map<String, String> headerParams = new HashMap<String, String>();
            headerParams.put("Authorization", "Bearer " + token);
            headerParams.put("Content-Type", "application/json");
            headerParams.put("X-PB-UnifiedErrorStructure", "true");
            HttpClientResult httpClientResult = HttpClientPoolUtil.doGet(url, headerParams, true);
            if (null == httpClientResult) {
                return new ApiLabelResponse(trackingNumber, null);
            }
            String responseJson = httpClientResult.getContent();
            if (StringUtils.isBlank(responseJson)) {
                return new ApiLabelResponse(trackingNumber, null);
            }
            if (responseJson.indexOf("errors") > -1) {
                return new ApiLabelResponse(trackingNumber, null);
            }
            JsonNode jsonNode = JsonConverter.tree(responseJson);
            // 追踪号节点
            JsonNode parcelTrackingNumberNode = jsonNode.findValue("parcelTrackingNumber");
            if (null == parcelTrackingNumberNode) {
                return new ApiLabelResponse(trackingNumber, null);
            }
            String parcelTrackingNumber = parcelTrackingNumberNode.asText();
            // 面单路径节点
            JsonNode documentsNode = jsonNode.findValue("documents");
            if (null == documentsNode) {
                return new ApiLabelResponse(trackingNumber, null);
            }
            JsonNode contentsNode = documentsNode.findValue("contents");
            if (null == contentsNode) {
                return new ApiLabelResponse(trackingNumber, null);
            }
            String contents = contentsNode.asText();// PDF
            if (StringUtils.isBlank(contents)) {
                return new ApiLabelResponse(trackingNumber, null);
            }
            String pdfName = parcelTrackingNumber + LabelSuffix.PDF.getSuffix();
            String centrePath = ApiUtil.getCurrentDay() + File.separator;
            String relativePath = DEFAULT_LABEL_FOLDER + File.separator + centrePath;
            String folderPath = DEFAULT_LABEL_ROOT + relativePath;
            if (!new File(folderPath).exists()) {
                new File(folderPath).mkdirs();
            }
            String fileUrl = folderPath + pdfName;
            ApiLabelResponse apiLabelResponse = new ApiLabelResponse();
            apiLabelResponse.setBarCode(parcelTrackingNumber);
            try {
                FileUtils.copyURLToFile(new URL(contents), new File(fileUrl));
                apiLabelResponse.setRelativePath(relativePath + pdfName);
            } catch (Exception e) {
                LOGGER.error("newgistics.copy.url.to.file.exception", e);
            }
            apiLabelResponse.setLabelPath(contents);
            apiLabelResponse.setLabelFormat(LabelFormat.PDF.getFormat());
            return apiLabelResponse;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public ApiLabelResponse label(String referenceNo, String trackNumber, Map<String, String> configure) {
        return null;
    }

    @Override
    public ApiLabelResponse labelRender(String trackNumber, ExpressOrderApi expressOrderApi) {
        return null;
    }

    @Override
    public void tracking(String trackNumber, Map<String, String> configure) {        
    }

    @Override
    public String trackNumber(String code, Map<String, String> configure) {
        return null;
    }  
    
    
    /**
     * access token
     * 
     * @return
     * @author Henry(fba02)
     * @version [版本号, Jul 3, 2019]
     * @see [类、类#方法、类#成员]
     */
    public String accessToken(String key, String secret, String generationTokenUrl) {
        final String split = ":";
        final String charset = "UTF-8";
        try {
            String authenticationHeader = new String(Base64.getEncoder().encode(new StringBuffer().append(key).append(split).append(secret).toString().getBytes(charset)), charset);
            Map<String, String> params = new HashMap<String, String>();
            params.put("grant_type", "client_credentials");
            Map<String, String> headers = new HashMap<String, String>();
            headers.put("Content-type", "application/x-www-form-urlencoded");
            headers.put("Authorization", "Basic " + authenticationHeader);
            HttpClientResult httpClientResult = HttpClientPoolUtil.doPost(generationTokenUrl, headers, params, true);
            if (null == httpClientResult) {
                return null;
            }
            String  responseJson = httpClientResult.getContent();
            if (StringUtils.isBlank( responseJson)) {
                return null;
            }
            LOGGER.info("newgistics.access.token:{}", responseJson);
            OAuthServiceResponce oAuthServiceResponce = new Gson().fromJson( responseJson, new TypeToken<OAuthServiceResponce>() {
            }.getType());
            if (null == oAuthServiceResponce) {
                return null;
            }
            return oAuthServiceResponce.getAccess_token();
        } catch (Exception e) {
            LOGGER.error("newgistics.accesstoken.exception", e);
            return null;
        }
    }    
    
    /**
     * @param args
     * @author Henry(fba02)
     * @version [版本号, 2020-10-21]
     * @see [类、类#方法、类#成员]
     */
//    public static void main(String[] args) {
//        try {
//            Class<?> clazz = Class.forName("com.tofba.service.impl.NewgisticsClient");
//            // key-secret-url
//            Method method = clazz.getMethod("accessToken", String.class, String.class, String.class);
//            Object object = method.invoke(clazz.newInstance(), new Object[] {"QBGb9IXpGGnyErh67moQ2mHeutQ7JDyP", "c0bJaaAgA04Ix8Es", "https://api.pitneybowes.com/oauth/token"});
//            if (null != object) {
//                System.out.println(object.toString());
//            }
//            FileUtils.copyURLToFile(new URL("http://192.168.180.22/upload/f6c13c3c9d8c4affb6ff225bc589508a.pdf"), new File("D:\\data\\f6c13c3c9d8c4affb6ff225bc589508a.pdf"));
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
//    }
}